import java.util.ArrayList;

/**
 * Class representing an image, stores data such as color matrix and mood,
 * provides some processing to simplify the neural network work
 */
public class Image {

	public static enum Mood { HAPPY, SAD, MISCHIEVOUS, MAD, UNKNOWN };
	public static final int IMAGE_WIDTH = 20;
	public static final int IMAGE_HEIGHT = 20;
	public static final int WHITE = 0;
	public static final int BLACK = 31;

	private Mood _mood;
	private String _name;
	private int[][] _imageMatrix;

	private ArrayList<Pixel> checkPixel = new ArrayList<Pixel>();
	private double angle = 0;

	public static Mood int2Mood(int i) {
		switch (i) {
		case 1:
			return Mood.HAPPY; 
		case 2:
			return Mood.SAD;
		case 3:
			return Mood.MISCHIEVOUS;
		case 4:
			return Mood.MAD;
		default:
			return Mood.UNKNOWN;
		}
	}

	public static int mood2Int(Mood m) {
		switch(m) {
		case HAPPY: return 1;
		case SAD: return 2;
		case MISCHIEVOUS: return 3;
		case MAD: return 4;
		default: return 5;
		}
	}

	public static int matrixSize() {
		return IMAGE_HEIGHT*IMAGE_WIDTH;
	}

	public Image(String name) {
		_name = name;
		_imageMatrix = new int[IMAGE_WIDTH][IMAGE_HEIGHT];
		_mood = Mood.UNKNOWN;
	}

	public String getName() {
		return _name;
	}

	public void setPixelValue(int value, int x, int y) {
		_imageMatrix[x][y] = value;
	}

	public int getPixelValue(int x, int y) {
		return _imageMatrix[x][y];
	}

	public int getPixelValue(int i) {
		int x = i % IMAGE_WIDTH;
		int y = i / IMAGE_HEIGHT;
		return _imageMatrix[x][y];
	}

	public void setMood(Mood m) {
		_mood = m;
	}

	public Mood getMood() {
		return _mood;
	}

	public int[][] getMatrix() {
		return _imageMatrix;
	}

	public void setMatrix(int[][] matrix) {
		_imageMatrix = matrix;
	}

	/**
	 * Turns the image around so that the eyes are at the top of the image
	 */
	public void turnImage(){
		int darkestPixel = BLACK;
		int pixelYvalue = 0;
		int x = 0;
		int y = 1;
		int yprimFirst = 0; 
		int yprimSecond = 0; 
		ArrayList<Pixel> firstEyebrow = new ArrayList<Pixel>();
		ArrayList<Pixel> secondEyebrow = new ArrayList<Pixel>();


		// Find first black pixel in image
		boolean found = false;
		for(int i = 0; i < IMAGE_WIDTH; i++) {
			for(int j = 0; j < IMAGE_HEIGHT; j++) {
				if(getPixelValue(i, j) == darkestPixel){ 
					Pixel pixel = new Pixel(i, j, darkestPixel);
					firstEyebrow.add(pixel);
					checkPixel.add(pixel);
					found = true;
					break;
				}
			}
			if(found){
				break;
			}

		}

		findEyebrow(firstEyebrow);

		// Find first black pixel in second eyebrow
		found = false; 
		for(int i = 0; i < IMAGE_WIDTH; i++) {
			for(int j = 0; j < IMAGE_HEIGHT; j++) {
				Pixel darkPixel = new Pixel(i, j, darkestPixel); 
				if(getPixelValue(i, j) == darkestPixel && !darkPixel.isInList(firstEyebrow)){ 
					Pixel pixel = new Pixel(i, j, darkestPixel);
					secondEyebrow.add(pixel);
					checkPixel.add(pixel);
					found = true;
					break;
				}
			}
			if(found){
				break;
			}

		}

		findEyebrow(secondEyebrow);

		int firstEyebrowCenterx = findEyebrowCenter(firstEyebrow, x); 
		int firstEyebrowCentery = findEyebrowCenter(firstEyebrow, y); 

		int secondEyebrowCenterx = findEyebrowCenter(secondEyebrow, x);  
		int secondEyebrowCentery = findEyebrowCenter(secondEyebrow, y); 

		//Find an angle so that the eyes are on same height
		for(double i = 0; i < 2*Math.PI; i = i + 0.00001){
			yprimFirst = (int) ((firstEyebrowCentery * Math.cos(i)) + (firstEyebrowCenterx * Math.sin(i)));
			yprimSecond = (int) ((secondEyebrowCentery * Math.cos(i)) + (secondEyebrowCenterx * Math.sin(i)));
			if(yprimFirst == yprimSecond){
				angle = i;
				break;
			}

		}
		
		// Create a rotation matrix and fill it with white
		int[][] rotatedImageMatrix = new int[IMAGE_WIDTH][IMAGE_HEIGHT];
		for(int i = 0; i < IMAGE_WIDTH; i++){
			for(int j = 0; j < IMAGE_HEIGHT; j++){
				rotatedImageMatrix[i][j] = WHITE; 
			}
		}
		
		fillRotateMatrix(rotatedImageMatrix);
		
		// Find y value of first black pixel in rotated matrix
		found = false;
		for(int i = 0; i < IMAGE_WIDTH; i++) {
			for(int j = 0; j < IMAGE_HEIGHT; j++) {
				if(rotatedImageMatrix[i][j] == darkestPixel){ 
					pixelYvalue = j;
					found = true;
					break;
				}
			}
			if(found){
				break;
			}

		}

		// If y value of first black pixel is in lower part of matrix, the picture is upside down
		boolean isUpsideDown = false;
		for(int i = 0; i < rotatedImageMatrix.length; i++){
			if(pixelYvalue > IMAGE_HEIGHT/2){
				isUpsideDown = true; 
			}
			
		}
		
		// If picture is upside down, turn it around 90 degrees
		if(isUpsideDown == true){
			angle = angle + Math.PI; 
			fillRotateMatrix(rotatedImageMatrix);
		}
		
		setMatrix(rotatedImageMatrix); 
		

	}

	/**
	 * Finds the whole eyebrow that is connected to a specified black pixel
	 * @param pixels A list of the pixels in the eyebrow
	 */
	private void findEyebrow(ArrayList<Pixel> pixels){
		ArrayList<Pixel> neighbours;
		
		//Find all black pixels in eyebrow
		boolean addedPixel = true;
		while(addedPixel){
			neighbours = ImageComputingHelper.getSurroundingPixel(checkPixel.get(0).getxValue(), 
					checkPixel.get(0).getyValue(), this);
			checkPixel.remove(0);
			for(int i = 0; i < neighbours.size(); i++){	
				if(neighbours.get(i) != null && neighbours.get(i).getColor() == BLACK){
					Pixel pixel = new Pixel(neighbours.get(i).getxValue(), 
							neighbours.get(i).getyValue(), neighbours.get(i).getColor());
					if(!pixel.isInList(pixels)){
						pixels.add(pixel);
						checkPixel.add(pixel);
						addedPixel = true;
					}

				}

			}
			if(checkPixel.isEmpty()){
				addedPixel = false;
			}

		}
	}
	
	/**
	 * Finds the coordinates for the center pixel of the eyebrow
	 * @param eyebrow The eyebrow of which it calculates the center
	 * @param coordinate A parameter to decide if function should return x or y coordinate for the center pixel
	 * @return an integer representing either the x or the y coordinate of the center pixel
	 */
	private int findEyebrowCenter(ArrayList<Pixel> eyebrow, int coordinate){ 
		if(coordinate == 0){
			return Math.abs(eyebrow.get(eyebrow.size()-1).getxValue() - 
				eyebrow.get(0).getxValue())/2 + eyebrow.get(0).getxValue();
		}
		else if(coordinate == 1){
			return Math.abs(eyebrow.get(eyebrow.size()-1).getyValue() - 
					eyebrow.get(0).getyValue())/2 + eyebrow.get(0).getyValue();
		}
		return 0; 
		
		
	}

	/**
	 * Calculates from the angle the new positions of the pixels in the image 
	 * and fills the rotated image matrix with these
	 * @param rotatedImageMatrix The rotated image matrix
	 */
	private void fillRotateMatrix(int[][] rotatedImageMatrix){
		// Use rotation matrix to turn matrix with an angle
		for(int i = 0; i < IMAGE_WIDTH; i++){
			for(int j = 0; j < IMAGE_HEIGHT; j++){
				int xprim = (int) (((i-IMAGE_WIDTH/2) * Math.cos(angle)) + ((j-IMAGE_HEIGHT/2) * Math.sin(angle) + 10));
				int yprim = (int) (((j-IMAGE_HEIGHT/2) * Math.cos(angle) + 10) - ((i-IMAGE_WIDTH/2) * Math.sin(angle)));
				if(xprim > 0 && xprim < IMAGE_WIDTH && yprim > 0 && yprim < IMAGE_HEIGHT){
					rotatedImageMatrix[i][j] = _imageMatrix[xprim][yprim]; 
				}
			}
		}
		
	}
	
	/**
	 * Translates the image matrix into a list
	 * @return The list representing the image matrix
	 */
	public ArrayList<Integer> getMatrixAsList() {
		ArrayList<Integer> valueList = new ArrayList<Integer>();
		for (int i = 0; i < IMAGE_WIDTH; i ++) {
			for (int j = 0; j < IMAGE_HEIGHT; j++) {
				valueList.add(_imageMatrix[i][j]);
			}
		}
		return valueList;
	}

}